#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <hpdf.h>

#include "resume_parse.h"
#include "unicode_to_pdf.h"

#define A4_WIDTH 595.276f
#define A4_HEIGHT 841.89f

struct horline {
	float sx;
	float sy;
	float len;
};

struct page_state {
	const char *font_path;
	float font_height;
	float spacing;
	float hmargin;
	float vmargin;
	float y_position;
	void *pdf_font;
};

const char *keywords[] = {
	"$doc_header",
	"$personal_header",
	"$experience_header",
	"$skills_header",
	"$education_header",
	"$comments_header",
	"$font_height",
	"$ttf_path",
	"$hmargin",
	"$vmargin",
	"$line_spacing",
	"%personal",
	"%education",
	"%experience",
	"%skills",
	"%comments"
};

enum key_id {
	K_DOC_HEADER = 0,
	K_PERSONAL_HEADER = 1,
	K_EXPERIENCE_HEADER,
	K_SKILLS_HEADER,
	K_EDUCATION_HEADER,
	K_COMMENTS_HEADER,
	K_FONT_HEIGHT,
	K_TTF_PATH,
	K_HMARGIN,
	K_VMARGIN,
	K_LINE_SPACING,
	K_PERSONAL,
	K_EDUCATION,
	K_EXPERIENCE,
	K_SKILLS,
	K_COMMENT,
	/* Should be last. */
	KEYS_COUNT
};

static void conv_cb(const char *buf, int bufsiz, void *ptr)
{
	HPDF_PageAttr a = (HPDF_PageAttr)ptr;
	a->stream->write_fn(a->stream, (const unsigned char *)buf, bufsiz);
}

static int
haru_ext_memcpy_utf(HPDF_Page p, const char *input, int bytes)
{
	const char tag_open[] = {'<'};
	const char tag_close[] = {'>', ' ', 'T', 'j', '\n'};
	HPDF_PageAttr a = p->attr;
	int used_count;
	a->stream->write_fn(a->stream, (const unsigned char *)tag_open,
			sizeof(tag_open));
	while (convert_to_pdf16(&input, &bytes, &used_count, conv_cb, a)
			== CONV_STATUS_MORE_DATA) ;
	a->stream->write_fn(a->stream, (const unsigned char *)tag_close,
			sizeof(tag_close));
	return 0;
}

static int
load_font(HPDF_Doc pdf, struct page_state *pg_param)
{
	const char *font_name = HPDF_LoadTTFontFromFile(pdf,
		pg_param->font_path,
		HPDF_FALSE);
	if (font_name == NULL)
		return -1;
	HPDF_UseUTFEncodings(pdf);
	pg_param->pdf_font = HPDF_GetFont(pdf, font_name, "UTF-8");
	if (pg_param->pdf_font == NULL)
		return -1;
	return 0;
}

static int
underlined(HPDF_Page pg, const char *what, struct horline *start_line)
{
	float l;
	haru_ext_memcpy_utf(pg, what, strlen(what));
	l = HPDF_Page_TextWidth(pg, what, NULL);
	start_line->sx = ((HPDF_PageAttr)pg->attr)->text_pos.x;
	start_line->sy = ((HPDF_PageAttr)pg->attr)->text_pos.y - 2.0f;
	start_line->len = l;
	HPDF_Page_MoveTextPos(pg, 0.0f, -(((HPDF_PageAttr)pg->attr)->
			gstate->font_size + 10.0f));
	return 0;
}

static int
draw_lines(HPDF_Page pg, struct horline *harr)
{
	for (; harr->len; ++ harr) {
		HPDF_Page_MoveTo(pg, harr->sx, harr->sy);
		HPDF_Page_LineTo(pg, harr->sx + harr->len, harr->sy);
	}
	HPDF_Page_Stroke(pg);
	return 0;
}

static int
center(HPDF_Page pg, const char *text,
	const struct page_state *pg_state)
{
	const float x = (A4_WIDTH - HPDF_Page_TextWidth(pg, text, NULL)) / 2.0f;
	HPDF_Page_MoveTextPos(pg, x, 0.0f);
	haru_ext_memcpy_utf(pg, text, strlen(text));
	HPDF_Page_MoveTextPos(pg, pg_state->hmargin - x,
			-(pg_state->font_height + pg_state->spacing));
	return 0;
}

static int
multiline(HPDF_Page pg, char *text, struct page_state *state)
{
	const char *nptr;
	const char *eptr;
	int is_over = 0;
	const HPDF_REAL pw =
		A4_WIDTH - (state->hmargin + state->hmargin / 2.0f);
	HPDF_UINT bytecount;
	HPDF_REAL rw;
	HPDF_PageAttr a = (HPDF_PageAttr)pg->attr;
	while (isspace(*text)) text ++;
	for (char *it = text; *it; ++ it)
		if (*it == '\n') *it = ' ';
	nptr = text;
	while (1) {
		bytecount = HPDF_Font_MeasureText(a->gstate->font,
			(const unsigned char *)nptr,
			strlen(nptr), pw, a->gstate->font_size,
			a->gstate->char_space, a->gstate->word_space,
			HPDF_TRUE, &rw);
		if (bytecount == 0) {
			return -1;
		}
		eptr = nptr + bytecount - 1;
		if (*(eptr + 1) == '\0') {
			++ eptr;
			is_over = 1;
		} else {
			if (!isspace(*eptr)) {
				return -1;
			}
		}
		haru_ext_memcpy_utf(pg, nptr, bytecount);
		HPDF_Page_MoveTextPos(pg, 0.0f,
			-(a->gstate->font_size + a->gstate->text_leading));
		if (is_over) break;
		nptr = eptr + 1;
	}
	return 0;
}

static char *
read_file_content_heap(const char *filename)
{
	FILE *f = fopen(filename, "r");
	long endpos;
	char *text_arr;
	size_t n;
	if (!f)
		return NULL;
	if (fseek(f, 0L, SEEK_END) == -1)
		return NULL;
	endpos = ftell(f);
	if (endpos == -1)
		return NULL;
	fseek(f, 0L, SEEK_SET);
	text_arr = malloc(endpos + 1);
	assert(text_arr != NULL);
	n = fread(text_arr, 1UL, endpos, f);
	fclose(f);
	if (n != (size_t)endpos) {
		free(text_arr);
		return NULL;
	}
	text_arr[n] = '\0';
	return text_arr;
}

static void
tokenized(HPDF_Page pg, const char *content, const char *o_tag,
	const char *c_tag, struct page_state *st)
{
	char mpool[4096];
	const size_t bufsize = strlen(content) + strlen(o_tag)
	+ strlen(c_tag) + 1;
	char *line = bufsize > sizeof(mpool) ? (char *)malloc(bufsize) : mpool;
	assert(line);
	snprintf(line, bufsize, "%s%s%s", o_tag, content, c_tag);
	multiline(pg, line, st);
	HPDF_Page_MoveTextPos(pg, 0.0f, -(HPDF_Page_GetTextLeading(pg) + 2.0f));
	if (sizeof(mpool) < bufsize) {
		fprintf(stderr,
		"warning, mpool size (%lu) was less than required: %lu\n",
		sizeof(mpool), bufsize);
		free(line);
	}
}

static int
skills(HPDF_Page pg, char *txt_skills, struct page_state *state)
{
	char *begin;
	char *end;
	begin = txt_skills;
	do {
		end = strstr(begin, "\n\n");
		if (end)
			*end = '\0';
		else
			break;
		tokenized(pg, begin, " \u2022 ", ";", state);
		begin = end + 2;
	} while (1);
	tokenized(pg, begin, " \u2022 ", ".", state);
	return 0;
}

static int
numbered(HPDF_Page pg, char *list_text, struct page_state *state)
{
	int n = 1;
	int is_done = 0;
	char *begin = list_text;
	char *end;
	char numstr[64];
	while (1) {
		end = strstr(begin, "\n\n");
		if (end) {
			*end = '\0';
		} else {
			end = strchr(begin, '\0');
			is_done = 1;
		}
		sprintf(numstr, " %i. ", n);
		tokenized(pg, begin, numstr, is_done ? "." : ";", state);
		if (is_done)
			break;
		begin = end + 2;
		++ n;
	}
	return 0;
}

static void
tr(HPDF_Page p, char *paragraphs, struct page_state *st)
{
	char *p_beg, *p_end;
	p_beg = paragraphs;
	while ((p_end = strstr(p_beg, "\n\n"))) {
		*p_end = '\0';
		multiline(p, p_beg, st);
		p_beg = p_end + 2;
		HPDF_Page_MoveTextPos(p, 0.0f, -HPDF_Page_GetTextLeading(p));
	}
	multiline(p, p_beg, st);
	HPDF_Page_MoveTextPos(p, 0.0f, -HPDF_Page_GetTextLeading(p));
}

int
main(int argc, char *argv[])
{
	struct page_state pg_state;
	HPDF_Doc pdf_doc;
	HPDF_Page page;
	struct horline lin[7];
	struct bounds doc_dict[KEYS_COUNT];
	struct field_data unord[KEYS_COUNT];
	struct field_data ord[KEYS_COUNT];
	struct key_search search_query;
	search_query.k_count = KEYS_COUNT;
	search_query.key_names = keywords;
	search_query.res_dict = doc_dict;
	search_query.sorted = ord;
	search_query.fld_srch = unord;
	char *doc_text =
		read_file_content_heap(argc > 1 ? argv[1] : "resume.txt");
	const char *output_filename = argc > 2 ? argv[2] : "resume.pdf";
	if (!doc_text) {
		perror("read_file_content_heap() failure");
		return 1;
	}
	if (keys_search(doc_text, &search_query) != SRCH_DONE) {
		fputs("markup parse failed\n", stderr);
		free(doc_text);
		return 1;
	}

	/* TEMPORARY CODE */
	for (unsigned i = 0; i < KEYS_COUNT; ++ i)
		((char *)doc_dict[i].endptr)[1] = '\0';

	pg_state.hmargin = atof(doc_dict[K_HMARGIN].beginptr);
	pg_state.vmargin = atof(doc_dict[K_VMARGIN].beginptr);
	pg_state.spacing = atof(doc_dict[K_LINE_SPACING].beginptr);
	pg_state.font_path = doc_dict[K_TTF_PATH].beginptr;
	pg_state.font_height = atof(doc_dict[K_FONT_HEIGHT].beginptr);
	/* END TEMPORARY CODE */

	pdf_doc = HPDF_NewEx(NULL, NULL, NULL, 0u, NULL);
	page = HPDF_AddPage(pdf_doc);
	HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);
	if (load_font(pdf_doc, &pg_state) == -1) {
		fputs("failed to load font\n", stderr);
		HPDF_Free(pdf_doc);
		return 1;
	}
	if (HPDF_Page_SetFontAndSize(page,
		pg_state.pdf_font, pg_state.font_height) !=
		HPDF_OK) {
			fputs("failed to set font/size\n", stderr);
			return 1;
		}
	HPDF_Page_SetTextLeading(page, pg_state.spacing);

	memset(lin, 0, sizeof(lin));

	HPDF_Page_BeginText(page);
	HPDF_Page_MoveTextPos(page, 0.0f, A4_HEIGHT - pg_state.vmargin);

	center(page, doc_dict[K_DOC_HEADER].beginptr, &pg_state);

	underlined(page, doc_dict[K_PERSONAL_HEADER].beginptr, lin);
	tr(page, (char *)doc_dict[K_PERSONAL].beginptr, &pg_state);

	underlined(page, doc_dict[K_EDUCATION_HEADER].beginptr, lin + 1);
	tr(page, (char *)doc_dict[K_EDUCATION].beginptr, &pg_state);

	underlined(page, doc_dict[K_EXPERIENCE_HEADER].beginptr, lin + 2);
	numbered(page, (char *)doc_dict[K_EXPERIENCE].beginptr, &pg_state);

	underlined(page, doc_dict[K_SKILLS_HEADER].beginptr, lin + 3);
	skills(page, (char *)doc_dict[K_SKILLS].beginptr, &pg_state);

	underlined(page, (char *)doc_dict[K_COMMENTS_HEADER].beginptr, lin + 4);
	tr(page, (char *)doc_dict[K_COMMENT].beginptr, &pg_state);

	HPDF_Page_EndText(page);

	draw_lines(page, lin);
	HPDF_SaveToFile(pdf_doc, output_filename);
	HPDF_Free(pdf_doc);
	free(doc_text);

	return 0;
}

